/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.dx.rop.code;

import com.android.dx.rop.cst.CstString;
import com.android.dx.util.Hex;

/**
 * Information about a source position for code, which includes both a line
 * number and original bytecode address.
 */
public final class SourcePosition {

	/** {@code non-null;} convenient "no information known" instance */
	public static final SourcePosition NO_INFO = new SourcePosition(null, -1,
			-1);

	/** {@code null-ok;} name of the file of origin or {@code null} if unknown */
	private final CstString sourceFile;

	/**
	 * {@code >= -1;} the bytecode address, or {@code -1} if that information is
	 * unknown
	 */
	private final int address;

	/**
	 * {@code >= -1;} the line number, or {@code -1} if that information is
	 * unknown
	 */
	private final int line;

	/**
	 * Constructs an instance.
	 * 
	 * @param sourceFile
	 *            {@code null-ok;} name of the file of origin or {@code null} if
	 *            unknown
	 * @param address
	 *            {@code >= -1;} original bytecode address or {@code -1} if
	 *            unknown
	 * @param line
	 *            {@code >= -1;} original line number or {@code -1} if unknown
	 */
	public SourcePosition(CstString sourceFile, int address, int line) {
		if (address < -1) {
			throw new IllegalArgumentException("address < -1");
		}

		if (line < -1) {
			throw new IllegalArgumentException("line < -1");
		}

		this.sourceFile = sourceFile;
		this.address = address;
		this.line = line;
	}

	/** {@inheritDoc} */
	@Override
	public String toString() {
		StringBuffer sb = new StringBuffer(50);

		if (sourceFile != null) {
			sb.append(sourceFile.toHuman());
			sb.append(":");
		}

		if (line >= 0) {
			sb.append(line);
		}

		sb.append('@');

		if (address < 0) {
			sb.append("????");
		} else {
			sb.append(Hex.u2(address));
		}

		return sb.toString();
	}

	/** {@inheritDoc} */
	@Override
	public boolean equals(Object other) {
		if (!(other instanceof SourcePosition)) {
			return false;
		}

		if (this == other) {
			return true;
		}

		SourcePosition pos = (SourcePosition) other;

		return (address == pos.address) && sameLineAndFile(pos);
	}

	/** {@inheritDoc} */
	@Override
	public int hashCode() {
		return sourceFile.hashCode() + address + line;
	}

	/**
	 * Returns whether the lines match between this instance and the one given.
	 * 
	 * @param other
	 *            {@code non-null;} the instance to compare to
	 * @return {@code true} iff the lines match
	 */
	public boolean sameLine(SourcePosition other) {
		return (line == other.line);
	}

	/**
	 * Returns whether the lines and files match between this instance and the
	 * one given.
	 * 
	 * @param other
	 *            {@code non-null;} the instance to compare to
	 * @return {@code true} iff the lines and files match
	 */
	public boolean sameLineAndFile(SourcePosition other) {
		return (line == other.line)
				&& ((sourceFile == other.sourceFile) || ((sourceFile != null) && sourceFile
						.equals(other.sourceFile)));
	}

	/**
	 * Gets the source file, if known.
	 * 
	 * @return {@code null-ok;} the source file or {@code null} if unknown
	 */
	public CstString getSourceFile() {
		return sourceFile;
	}

	/**
	 * Gets the original bytecode address.
	 * 
	 * @return {@code >= -1;} the address or {@code -1} if unknown
	 */
	public int getAddress() {
		return address;
	}

	/**
	 * Gets the original line number.
	 * 
	 * @return {@code >= -1;} the original line number or {@code -1} if unknown
	 */
	public int getLine() {
		return line;
	}
}
